/**
 * @作者 zw
 * @微信公号 搬砖暗夜码农
 * @开源项目 $ https://gitee.com/zhouwei5208888/spring-boot-distributed
 * @我的博客 $ http://itzhouwei.com
 * @创建时间 2024/6/23 上午10:46
 */
package com.itzhouwei.filter;

import com.google.gson.Gson;
import com.itzhouwei.base.BaseInfoProperties;
import com.itzhouwei.enums.ResponseStatusEnum;
import com.itzhouwei.result.GraceJSONResult;
import com.itzhouwei.utils.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * @description: $IPLimitFilter 对请求进行处理
 * @author：zw，微信公共号：yingshengzw
 * @date: 2021/8/28
 * @Copyright：公众号：搬砖暗夜码农 | 博客：https://itzhouwei.com - 沉淀、学习、分享、成长，让自己和他人都能有所收获！
 */
@Slf4j
@SuppressWarnings("all")
@RefreshScope
@Component
public class IPLimitFilter extends BaseInfoProperties implements GlobalFilter, Ordered {

    @Value("${blackIp.contoinueCounts:3}")
    private Integer contoinueCounts = 3;

    @Value("${blackIp.timeInterval:20}")
    private Integer timeInterval = 20;
    @Value("${blackIp.limitTimes:30}")
    private Integer limitTimes = 30;

    /*****
     * 判断某个请求的ip 在20秒内的请求次数是否超过3次
     * 如果超过3次  则限制访问30秒
     * 等待30秒静默后 才能够继续恢复访问
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("当前的执行顺序order为1");

        log.info("当前的执行顺序order为1contoinueCounts" +contoinueCounts);
        log.info("当前的执行顺序order为1timeInterval" +timeInterval);
        log.info("limitTimes"+limitTimes);

        return doLimit(exchange, chain);

    }


    /***
     * 限制请求ip 请求次数的判断
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) {

        //1、获得请求
        ServerHttpRequest request = exchange.getRequest();

        String ip = IPUtil.getIP(request);
        //正常的ip定义
        final String ipRedisKey = "gateway-ip:" + ip;
        //被拦截的黑名单ip 如果在redis 中存在 则表示目前被关掉
        final String ipRedisLimitKey = "gateway:zw:" + ip;
        //获得当前的ip 并且查询还剩下多少时间 如果时间存在 大于0  则表示还在黑名单当中
        long limitLeftTimes = redis.ttl(ipRedisLimitKey);

        if (limitLeftTimes > 0) {
            // 存在返回报错信息
            return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
        }
        //在redis 当中获取ip的累加
        long requestCounts = redis.increment(ipRedisKey, 1);
        /***
         * 判断如果是第一次进来  也就是从0 开始技术 则初期访问就是1
         * 需要设置间隔的时间  也就是连续请求的次数的间隔时间
         */
        if (requestCounts == 1) {
            redis.expire(ipRedisKey, timeInterval);
        }
        //如果还能获取请求正常次数  说明用户的连续请求落在限定的时间之内
        //一旦请求次数超过限定的连续访问次数  则需要限制当前的ip
        if (requestCounts  > contoinueCounts) {
            //限制ip 访问的时间
            log.error("{}"+ip+" "+ipRedisLimitKey);
            redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes);
            //终止请求
           return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
        }
        //默认放行请求到后续的服务
        return chain.filter(exchange);

    }


    /***
     * 重新包装并且返回对应的错误信息
     * @param exchange
     * @param statusEnum
     * @return
     */
    public Mono<Void> renderErrorMsg(ServerWebExchange exchange,
                                     ResponseStatusEnum statusEnum) {

        //1、获得相应response
        ServerHttpResponse response = exchange.getResponse();

        //构建返回jsonResult
        GraceJSONResult jsonResult = GraceJSONResult.errorCustom(statusEnum);

        //设置header 类型
        if (!response.getHeaders().containsKey("Content-Type")) {
            response.getHeaders().add("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);
        }
        //4. 修改对应的状态码 操作
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);

        //5、转换json
        String result = new Gson().toJson(jsonResult);

        //写入数据到response
        DataBuffer buffer = response
                .bufferFactory()
                .wrap(result.getBytes(StandardCharsets.UTF_8));

        return response.writeWith(Mono.just(buffer));
    }


    /****
     * 过滤器的顺序  数字越小则优先级越大
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }


}
